// HSS.IO.FileUtil.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       FileUtil.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.IO
{
	#region Using Directives
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Security;
	using System.Security.AccessControl;
	using System.Text;
	using System.Text.RegularExpressions;
	#endregion

	#region FileUtil
	/// <summary>
	/// FileUtil Class contains helper methods when working with files and directories
	/// </summary>
	public static class FileUtil
	{
		#region Constants
		const string fileNameRegEx = @"^([a-zA-Z]:)?[^:]+$";
		#endregion

		#region Fields
		static readonly char[] separators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, Path.VolumeSeparatorChar };
		/// <summary>
		/// This is an arbitrary limitation built into the .NET Framework. Windows supports paths up to 32k length.
		/// </summary>
		public static readonly int MaxPathLength = 260;
		#endregion

		#region Methods

		/// <summary>
		/// Combine multiple paths
		/// </summary>
		/// <param name="paths">The string array of paths</param>
		/// <returns>The single combined path</returns>
		public static string Combine(params string[] paths)
		{

			if (paths == null || paths.Length == 0)
				return String.Empty;

			string result = paths[0];
			for (int i = 1; i < paths.Length; i++)
				result = Path.Combine(result, paths[i]);

			return result;
		}
		/// <summary>
		/// Removes unsafe Path characters from a string
		/// </summary>
		/// <param name="unsafePathName">The string to clean</param>
		/// <returns>The cleaned string</returns>
		public static string GetSafePath(string unsafePathName)
		{
			List<char> safeString = new List<char>();
			bool okChar = false;
			foreach (char c1 in unsafePathName.ToCharArray())
			{
				okChar = true;
				foreach (char c2 in Path.GetInvalidPathChars())
				{
					if (c1.Equals(c2))
					{
						okChar = false;
						break;
					}
				}
				if (okChar)
					safeString.Add(c1);
			}

			return new string(safeString.ToArray());
		}
		/// <summary>
		/// Removes unsafe File Name characters from a string
		/// </summary>
		/// <param name="unsafeFileName">The string to clean</param>
		/// <returns>The cleaned string</returns>
		public static string GetSafeFileName(string unsafeFileName)
		{
			List<char> safeString = new List<char>();
			bool okChar = false;
			foreach (char c1 in unsafeFileName.ToCharArray())
			{
				okChar = true;
				foreach (char c2 in Path.GetInvalidFileNameChars())
				{
					if (c1.Equals(c2))
					{
						okChar = false;
						break;
					}
				}
				if (okChar)
					safeString.Add(c1);
			}

			return new string(safeString.ToArray());
		}
		/// <summary>
		/// Attempts to locate the common portion of the two paths
		/// </summary>
		/// <param name="path1">The first path to compare</param>
		/// <param name="path2">The second path to compare</param>
		/// <returns>The common portion of the two paths</returns>
		public static string GetCommonBasePath(string path1, string path2)
		{
			if (path1 == null || path2 == null) return null;
			if (IsUrl(path1) || IsUrl(path2)) return null;

			path1 = Path.GetFullPath(path1);
			path2 = Path.GetFullPath(path2);

			string[] aPath = path1.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			string[] bPath = path2.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			StringBuilder result = new StringBuilder();
			int indx = 0;
			for (; indx < Math.Min(bPath.Length, aPath.Length); ++indx)
			{
				if (bPath[indx].Equals(aPath[indx], StringComparison.OrdinalIgnoreCase))
				{
					if (result.Length > 0) result.Append(Path.DirectorySeparatorChar);
					result.Append(aPath[indx]);
				}
				else
				{
					break;
				}
			}
			if (indx == 0)
				return null;
			else
				return result.ToString();
		}
		/// <summary>
		/// Converts a given absolute path and a given base path to a path that leads
		/// from the base path to the absoulte path. (as a relative path)
		/// </summary>
		/// <param name="basePath">The base path to service as the relative source</param>
		/// <param name="absPath">The absolute path to convert</param>
		/// <returns>The relative path to the absolute path</returns>
		public static string GetRelativePath(string basePath, string absPath)
		{
			if (IsUrl(absPath) || IsUrl(basePath))
				return absPath;

			try
			{
				basePath = Path.GetFullPath(basePath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
				absPath = Path.GetFullPath(absPath);
			}
			catch (Exception ex)
			{
				throw new ArgumentException("GetRelativePath error '" + basePath + "' -> '" + absPath + "'", ex);
			}

			string[] bPath = basePath.Split(separators);
			string[] aPath = absPath.Split(separators);
			int index = 0;
			for (; index < Math.Min(bPath.Length, aPath.Length); ++index)
			{
				if (!bPath[index].Equals(aPath[index], StringComparison.OrdinalIgnoreCase))
					break;
			}

			if (index == 0)
				return absPath;

			StringBuilder relPath = new StringBuilder();

			if (index != bPath.Length)
			{
				for (int i = index; i < bPath.Length; ++i)
				{
					relPath.Append("..");
					relPath.Append(Path.DirectorySeparatorChar);
				}
			}

			relPath.Append(String.Join(Path.DirectorySeparatorChar.ToString(), aPath, index, aPath.Length - index));

			return relPath.ToString();

		}
		/// <summary>
		/// Converts a given relative path and a given base path to an absoulte path
		/// by combining to the two together and calling <see cref="Path.GetFullPath"/>().
		/// </summary>
		/// <param name="basePath">The base path</param>
		/// <param name="relativePath">The relative path</param>
		/// <returns>The combined and rooted absolute full path</returns>
		public static string GetAbsolutePath(string basePath, string relativePath)
		{
			return Path.GetFullPath(Path.Combine(basePath, relativePath));
		}
		/// <summary>
		/// Replaces the <paramref name="fileBasePath"/> portion of <paramref name="fileFullPath"/> with <paramref name="newBasePath"/>.
		/// </summary>
		/// <param name="fileFullPath">The full file path to be changed</param>
		/// <param name="fileBasePath">The files base path to be replaced</param>
		/// <param name="newBasePath">The new base path for the file</param>
		/// <returns>The modified file path using the new base path unless the fileBasePath does not match, then returns
		/// the fileFullPath; otherwise simply returns the newBasePath if fileFullPath equals fileBasePath</returns>
		public static string ReplaceBasePath(string fileFullPath, string fileBasePath, string newBasePath)
		{
			fileFullPath = Path.GetFullPath(fileFullPath);
			fileBasePath = Path.GetFullPath(fileBasePath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
			newBasePath = Path.GetFullPath(newBasePath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));

			if (PathStartsWith(fileBasePath, fileFullPath))
			{
				if (fileFullPath.Length == fileBasePath.Length)
					return newBasePath;

				return Path.Combine(newBasePath, fileFullPath.Substring(fileBasePath.Length + 1));
			}

			return fileFullPath;
		}
		/// <summary>
		/// Renames the base directory for a given file
		/// </summary>
		/// <param name="fileName">The file to rebase</param>
		/// <param name="oldDirectory">The current directory</param>
		/// <param name="newDirectory">The new directory</param>
		/// <returns>Returns the FileName with the new base directory</returns>
		public static string RenameBaseDirectory(string fileName, string oldDirectory, string newDirectory)
		{
			fileName = Path.GetFullPath(fileName);
			oldDirectory = Path.GetFullPath(oldDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
			newDirectory = Path.GetFullPath(newDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
			if (IsBaseDirectory(oldDirectory, fileName))
			{
				if (fileName.Length == oldDirectory.Length)
					return newDirectory;
				return Path.Combine(newDirectory, fileName.Substring(oldDirectory.Length + 1));
			}
			return fileName;
		}

		/// <summary>
		/// Checks to see if the base directory for <paramref name="testDirectory"/> is equal to the <paramref name="baseDirectory"/> provided
		/// </summary>
		/// <param name="baseDirectory">The base to compare</param>
		/// <param name="testDirectory">The directory to check</param>
		/// <returns>true of the testDirectory has the same baseDirectory</returns>
		public static bool IsBaseDirectory(string baseDirectory, string testDirectory)
		{
			try
			{
				baseDirectory = Path.GetFullPath(baseDirectory).ToUpperInvariant();
				testDirectory = Path.GetFullPath(testDirectory).ToUpperInvariant();
				baseDirectory = baseDirectory.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
				testDirectory = testDirectory.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);

				if (baseDirectory[baseDirectory.Length - 1] != Path.DirectorySeparatorChar)
					baseDirectory += Path.DirectorySeparatorChar;
				if (testDirectory[testDirectory.Length - 1] != Path.DirectorySeparatorChar)
					testDirectory += Path.DirectorySeparatorChar;

				return testDirectory.StartsWith(baseDirectory);
			}
			catch (Exception)
			{
				return false;
			}
		}
		/// <summary>
		/// Determines if a file is accessible and can be opened for read access
		/// </summary>
		/// <param name="fileName">The file name and location</param>
		/// <returns>true if the file can be opened</returns>
		/// <exception cref="UnauthorizedAccessException">Rethrown if encoutered</exception>
		/// <exception cref="SecurityException">Rethrown if encoutered</exception>
		public static bool IsFileAvailable(string fileName)
		{
			return IsFileAvailable(fileName, FileMode.Open, FileAccess.Read);
		}
		/// <summary>
		/// Determines if a file is accessible and can be opened with the access method specified
		/// </summary>
		/// <param name="fileName">The file name and location</param>
		/// <param name="fileAccessMethod">The File Access method used to evaluate a file</param>
		/// <returns>true if the file can be opened</returns>
		/// <exception cref="UnauthorizedAccessException">Rethrown if encoutered</exception>
		/// <exception cref="SecurityException">Rethrown if encoutered</exception>
		public static bool IsFileAvailable(string fileName, FileAccess fileAccessMethod)
		{
			return IsFileAvailable(fileName, FileMode.Open, fileAccessMethod);
		}
		/// <summary>
		/// Determines if a file is accessible and can be opened using the desired FileMode 
		/// and FileAccess parameters
		/// </summary>
		/// <param name="fileName">The file name and location</param>
		/// <param name="accessMode">The desired FileMode</param>
		/// <param name="fileAccessMethod">The File Access method used to evaluate a file</param> 
		/// <returns>true if the file can be opened</returns>
		/// <exception cref="UnauthorizedAccessException">Rethrown if encoutered</exception>
		/// <exception cref="SecurityException">Rethrown if encoutered</exception>
		public static bool IsFileAvailable(string fileName, FileMode accessMode, FileAccess fileAccessMethod)
		{
			if (!File.Exists(fileName))
				return false;
			try
			{
				using (FileStream f = new FileStream(fileName, accessMode, fileAccessMethod))
					return true;
			}
			catch (UnauthorizedAccessException) { throw; }
			catch (SecurityException) { throw; }
			catch { return false; }
		}
		/// <summary>
		/// Determines if a path is a URL by looking for a ':' anywhere after the first two characters.
		/// </summary>
		/// <param name="path">The path to check</param>
		/// <returns>true if the path is a Url; otherwise false</returns>
		public static bool IsUrl(string path)
		{
			return path.IndexOf(':') >= 2;
		}
		/// <summary>
		/// Determines if the two file names are equal
		/// </summary>
		/// <param name="fileName1">The first file name to check</param>
		/// <param name="fileName2">The second file name to check</param>
		/// <returns>true if the file names are equal; otherwise false</returns>
		public static bool IsEqualFileName(string fileName1, string fileName2)
		{
			if (string.IsNullOrEmpty(fileName1) || string.IsNullOrEmpty(fileName2)) return false;

			char lastChar;
			lastChar = fileName1[fileName1.Length - 1];
			if (lastChar == Path.DirectorySeparatorChar || lastChar == Path.AltDirectorySeparatorChar)
				fileName1 = fileName1.Substring(0, fileName1.Length - 1);
			lastChar = fileName2[fileName2.Length - 1];
			if (lastChar == Path.DirectorySeparatorChar || lastChar == Path.AltDirectorySeparatorChar)
				fileName2 = fileName2.Substring(0, fileName2.Length - 1);

			try
			{
				if (fileName1.Length < 2 || fileName1[1] != ':' || fileName1.IndexOf("/.") >= 0 || fileName1.IndexOf("\\.") >= 0)
					fileName1 = Path.GetFullPath(fileName1);
				if (fileName2.Length < 2 || fileName2[1] != ':' || fileName2.IndexOf("/.") >= 0 || fileName2.IndexOf("\\.") >= 0)
					fileName2 = Path.GetFullPath(fileName2);
			}
			catch (Exception) { }

			return string.Equals(fileName1, fileName2, StringComparison.OrdinalIgnoreCase);
		}
		/// <summary>
		/// Checks if a file name is valid.
		/// </summary>
		/// <param name="fileName">the file name to check</param>
		/// <returns>true if the file name is valid; otherwise false</returns>
		public static bool IsValidFileName(string fileName)
		{
			if (fileName == null || fileName.Length == 0 || fileName.Length >= MaxPathLength)
				return false;

			// platform independend : check for invalid path chars
			if (fileName.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
				return false;

			if (fileName.IndexOf('?') >= 0 || fileName.IndexOf('*') >= 0)
				return false;

			if (!Regex.IsMatch(fileName, fileNameRegEx))
				return false;

			// platform dependend : Check for invalid file names (DOS)
			// this routine checks for follwing bad file names :
			// CON, PRN, AUX, NUL, COM1-9 and LPT1-9

			string nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
			if (nameWithoutExtension != null)
				nameWithoutExtension = nameWithoutExtension.ToUpperInvariant();

			if (nameWithoutExtension == "CON" ||
				nameWithoutExtension == "PRN" ||
				nameWithoutExtension == "AUX" ||
				nameWithoutExtension == "NUL")
				return false;

			char ch = nameWithoutExtension.Length == 4 ? nameWithoutExtension[3] : '\0';

			return !((nameWithoutExtension.StartsWith("COM") ||
					  nameWithoutExtension.StartsWith("LPT")) &&
					 Char.IsDigit(ch));
		}
		/// <summary>
		/// Checks that a single directory name (not the full path) is valid.
		/// </summary>
		/// <param name="name">The name to check</param>
		/// <returns>true if the name is a valid directory name; otherwise false</returns>
		public static bool IsValidDirectoryName(string name)
		{
			if (!IsValidFileName(name))
				return false;

			if (name.IndexOfAny(new char[] { Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar }) >= 0)
				return false;

			if (name.Trim(' ').Length == 0)
				return false;

			return true;
		}
		/// <summary>
		/// Checks if a file name is a directory
		/// </summary>
		/// <param name="fileName">The file name to check</param>
		/// <returns>true if the file name is a directory; otherwise false</returns>
		public static bool IsDirectory(string fileName)
		{
			if (!Directory.Exists(fileName))
				return false;
			FileAttributes attr = File.GetAttributes(fileName);
			return (attr & FileAttributes.Directory) != 0;
		}
		/// <summary>
		/// Determines whether the beginning of the specified <paramref name="path"/> matches the provided <paramref name="basePath"/>
		/// </summary>
		/// <param name="basePath">The Directory Path that serves as the base path</param>
		/// <param name="path">The File or Directory to check</param>
		/// <returns>true if the <paramref name="path"/> starts with the base path; otherwise false</returns>
		/// <remarks>Before comparing the paths, a call to <c>Path.GetFullPath()</c> is called on both values
		/// to ensure they're both absolute and rooted, such as "\test\data" becomes "c:\test\data".</remarks>
		public static bool PathStartsWith(string basePath, string path)
		{
			try
			{
				basePath = Path.GetFullPath(basePath).ToUpperInvariant();
				basePath = basePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
				if (basePath[basePath.Length - 1] != Path.DirectorySeparatorChar)
					basePath += Path.DirectorySeparatorChar;

				path = Path.GetFullPath(path).ToUpperInvariant();
				path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
				if (path[path.Length - 1] != Path.DirectorySeparatorChar)
					path += Path.DirectorySeparatorChar;

				return path.StartsWith(basePath);
			}
			catch (Exception)
			{
				return false;
			}
		}
		/// <summary>
		/// Determines if the provided file name matches the given patter
		/// </summary>
		/// <param name="fileName">The file name to check</param>
		/// <param name="pattern">The patter used for comparison</param>
		/// <returns>true if the file name matches the pattern</returns>
		public static bool MatchesPattern(string fileName, string pattern)
		{
			fileName = fileName.ToUpper();
			pattern = pattern.ToUpper();
			string[] patterns = pattern.Split(';');
			foreach (string p in patterns)
			{
				if (Match(fileName, p))
				{
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Compare two files to determine if they are equal
		/// </summary>
		/// <param name="file1">The first file used in the comparison</param>
		/// <param name="file2">The second file used in the comparison</param>
		/// <returns>true if the files are the same; otherwise false</returns>
		public static bool FileCompare(string file1, string file2)
		{
			int file1byte;
			int file2byte;
			FileStream fs1 = null;
			FileStream fs2 = null;

			// Determine if the same file was referenced two times.
			if (file1 == file2)
			{
				// Return true to indicate that the files are the same.
				return true;
			}

			// If either file doesn't exist then they are not the same
			if (!File.Exists(file1))
				return false;

			if (!File.Exists(file2))
				return false;

			try
			{

				// Open the two files.
				fs1 = new FileStream(file1, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
				fs2 = new FileStream(file2, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

				// Check the file sizes. If they are not the same, the files 
				// are not the same.
				if (fs1.Length != fs2.Length)
				{
					// Close the file
					fs1.Close();
					fs2.Close();

					// Return false to indicate files are different
					return false;
				}

				// Read and compare a byte from each file until either a
				// non-matching set of bytes is found or until the end of
				// file1 is reached.
				do
				{
					// Read one byte from each file.
					file1byte = fs1.ReadByte();
					file2byte = fs2.ReadByte();
				}
				while ((file1byte == file2byte) && (file1byte != -1));

				// Close the files.
				fs1.Close();
				fs2.Close();

				// Return the success of the comparison. "file1byte" is 
				// equal to "file2byte" at this point only if the files are 
				// the same.
				return ((file1byte - file2byte) == 0);

			}
			finally
			{
				if (null != fs1)
				{
					fs1.Dispose();
				}
				if (null != fs2)
				{
					fs2.Dispose();
				}
			}
		}

		/// <summary>
		/// Evaluates if a directory exists and if not, creates the directory
		/// </summary>
		/// <param name="directory">The path to ensure exists</param>
		public static void EnsureDirectoryExist(string directory)
		{
			if (!Directory.Exists(directory))
				Directory.CreateDirectory(directory);
		}
		/// <summary>
		/// Evaluates if a directory exists and if not, creates the directory. In addition allows you to specify Security access for the directory.
		/// </summary>
		/// <param name="directory">The path to ensure exists</param>
		/// <param name="identity">The identity to allow access (i.e., Users or Administrators)</param>
		/// <param name="rights">The rights to assign</param>
		/// <remarks>Provide for the abillity to set the access permission to the directory. If the Folder already exists, then will append the new access security</remarks>
		public static void EnsureDirectoryExist(string directory, string identity, FileSystemRights rights)
		{
			DirectoryInfo dirInfo = new DirectoryInfo(directory);
			DirectorySecurity dirSec = null;
			FileSystemAccessRule rule = new FileSystemAccessRule(identity, rights, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow);

			if (!dirInfo.Exists)
			{
				dirSec = new DirectorySecurity();
				dirSec.AddAccessRule(rule);
				dirInfo.Create(dirSec);
			}
			else
			{
				dirSec = dirInfo.GetAccessControl(AccessControlSections.Access);
				dirSec.AddAccessRule(rule);
				dirInfo.SetAccessControl(dirSec);
			}
		}
		/// <summary>
		/// Evaluates if a directory exists and if not, creates the directory. In addition allows you to specify Security access for the directory.
		/// </summary>
		/// <param name="directory">The path to ensure exists</param>
		/// <param name="identity">The identity to allow access (i.e., Users or Administrators)</param>
		/// <param name="rights">The rights to assign</param>
		/// <param name="accessControl">The control for the given rights, Allow or Deny</param>
		/// <remarks>Provide for the abillity to set the access permission to the directory. If the Folder already exists, then will append the new access security</remarks>
		public static void EnsureDirectoryExist(string directory, string identity, FileSystemRights rights, AccessControlType accessControl)
		{
			DirectoryInfo dirInfo = new DirectoryInfo(directory);
			DirectorySecurity dirSec = null;
			FileSystemAccessRule rule = new FileSystemAccessRule(identity, rights, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, accessControl);

			if (!dirInfo.Exists)
			{
				dirSec = new DirectorySecurity();
				dirSec.AddAccessRule(rule);
				dirInfo.Create(dirSec);
			}
			else
			{
				dirSec = dirInfo.GetAccessControl(AccessControlSections.Access);
				dirSec.AddAccessRule(rule);
				dirInfo.SetAccessControl(dirSec);
			}
		}
		/// <summary>
		/// Performs a deep copy of all directories and files, optionally specifying whether or not to overwrite existing files
		/// </summary>
		/// <param name="sourceDirectory">The directory to copy from</param>
		/// <param name="destinationDirectory">The directory to copy to</param>
		/// <param name="overwrite">Indicates whether or not to overwrite existing files</param>
		public static void DeepCopy(string sourceDirectory, string destinationDirectory, bool overwrite)
		{
			EnsureDirectoryExist(destinationDirectory);

			foreach (string fileName in Directory.GetFiles(sourceDirectory))
				File.Copy(fileName, Path.Combine(destinationDirectory, Path.GetFileName(fileName)), overwrite);

			foreach (string directoryName in Directory.GetDirectories(sourceDirectory))
				DeepCopy(directoryName, Path.Combine(destinationDirectory, Path.GetFileName(directoryName)), overwrite);
		}

		/// <summary>
		/// Finds all files which are valid for the <paramref name="fileMask"/> in the specified <paramref name="directory"/>
		/// and all subdirectories (if <paramref name="searchSubdirectories"/> is true).
		/// If <paramref name="ignoreHidden"/> is true, hidden files and folders are ignored.
		/// </summary>
		/// <param name="directory">The directory the search in</param>
		/// <param name="fileMask">The mask criteria for finding files</param>
		/// <param name="searchSubdirectories">Indicates whether or not to search sub-directories</param>
		/// <param name="ignoreHidden">Indicates whether or not to ignore (exclude) hidden files and folders</param>
		/// <returns>A List&lt;string&gt; collection containing the files found if any</returns>
		public static List<string> SearchDirectory(string directory, string fileMask, bool searchSubdirectories, bool ignoreHidden)
		{
			List<string> collection = new List<string>();
			SearchDirectory(directory, fileMask, collection, searchSubdirectories, ignoreHidden);
			return collection;
		}
		/// <summary>
		/// Finds all files which are valid for the <paramref name="fileMask"/> in the specified <paramref name="directory"/>
		/// and all subdirectories (if <paramref name="searchSubdirectories"/> is true).
		/// </summary>
		/// <param name="directory">The directory the search in</param>
		/// <param name="fileMask">The mask criteria for finding files</param>
		/// <param name="searchSubdirectories">Indicates whether or not to search sub-directories</param>
		/// <returns>A List&lt;string&gt; collection containing the files found if any</returns>
		public static List<string> SearchDirectory(string directory, string fileMask, bool searchSubdirectories)
		{
			return SearchDirectory(directory, fileMask, searchSubdirectories, true);
		}
		/// <summary>
		/// Finds all files which are valid for the <paramref name="fileMask"/> in the specified <paramref name="directory"/>.
		/// </summary>
		/// <param name="directory">The directory the search in</param>
		/// <param name="fileMask">The mask criteria for finding files</param>
		/// <returns>A List&lt;string&gt; collection containing the files found if any</returns>
		public static List<string> SearchDirectory(string directory, string fileMask)
		{
			return SearchDirectory(directory, fileMask, true, true);
		}

		/// <summary>
		/// Finds all files which are valid for the <paramref name="fileMask"/> in the specified <paramref name="directory"/>
		/// and all subdirectories (if <paramref name="searchSubdirectories"/> is true).
		/// The found files are added to the List&lt;string&gt; <paramref name="collection"/>.
		/// If <paramref name="ignoreHidden"/> is true, hidden files and folders are ignored.
		/// </summary>
		/// <param name="collection">The collection to populate with found files</param>
		/// <param name="directory">The directory the search in</param>
		/// <param name="fileMask">The mask criteria for finding files</param>
		/// <param name="ignoreHidden">Indicates whether or not to ignore (exclude) hidden files and folders</param>
		/// <param name="searchSubdirectories">Indicate whether or not to search sub-directories</param>
		static void SearchDirectory(string directory, string fileMask, List<string> collection, bool searchSubdirectories, bool ignoreHidden)
		{
			// If Directory.GetFiles() searches the 8.3 name as well as the full name so if the fileMask is 
			// "*.xpt" it will return "Template.xpt~"
			bool isExtMatch = Regex.IsMatch(fileMask, @"^\*\..{3}$");
			string ext = null;
			string[] file = Directory.GetFiles(directory, fileMask);
			if (isExtMatch) ext = fileMask.Remove(0, 1);

			foreach (string f in file)
			{
				if (ignoreHidden && (File.GetAttributes(f) & FileAttributes.Hidden) == FileAttributes.Hidden)
				{
					continue;
				}
				if (isExtMatch && Path.GetExtension(f) != ext) continue;

				collection.Add(f);
			}

			if (searchSubdirectories)
			{
				string[] dir = Directory.GetDirectories(directory);
				foreach (string d in dir)
				{
					if (ignoreHidden && (File.GetAttributes(d) & FileAttributes.Hidden) == FileAttributes.Hidden)
					{
						continue;
					}
					SearchDirectory(d, fileMask, collection, searchSubdirectories, ignoreHidden);
				}
			}
		}
		static bool MatchN(string src, int srcidx, string pattern, int patidx)
		{
			int patlen = pattern.Length;
			int srclen = src.Length;
			char next_char;

			for (; ; )
			{
				if (patidx == patlen)
					return (srcidx == srclen);
				next_char = pattern[patidx++];
				if (next_char == '?')
				{
					if (srcidx == src.Length)
						return false;
					srcidx++;
				}
				else if (next_char != '*')
				{
					if ((srcidx == src.Length) || (src[srcidx] != next_char))
						return false;
					srcidx++;
				}
				else
				{
					if (patidx == pattern.Length)
						return true;
					while (srcidx < srclen)
					{
						if (MatchN(src, srcidx, pattern, patidx))
							return true;
						srcidx++;
					}
					return false;
				}
			}
		}
		static bool Match(string src, string pattern)
		{
			if (pattern[0] == '*')
			{
				// common case optimization
				int i = pattern.Length;
				int j = src.Length;
				while (--i > 0)
				{
					if (pattern[i] == '*')
						return MatchN(src, 0, pattern, 0);
					if (j-- == 0)
						return false;
					if ((pattern[i] != src[j]) && (pattern[i] != '?'))
						return false;
				}
				return true;
			}
			return MatchN(src, 0, pattern, 0);
		}

		#endregion
	}
	#endregion
}